﻿using System;
using System.Globalization;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Colorful.Control.Controls
{
    public class ColorSwapper
    {
        public static Brush SwapColors(Brush brush, ColorCallback colorCallback)
        {
            if (colorCallback == null)
                throw new ArgumentNullException("colorCallback");

            Brush br = brush;

            if (brush != null)
            {
                br = brush.Clone();
                SwapColorsWithoutCloning(br, colorCallback);
                br.Freeze();
            }

            return br;
        }

        public static Drawing SwapColors(Drawing drawing, ColorCallback colorCallback)
        {
            if (colorCallback == null)
                throw new ArgumentNullException("colorCallback");

            Drawing dwg = drawing;
            if (drawing != null)
            {
                dwg = drawing.Clone();
                SwapColorsWithoutCloning(dwg, colorCallback);
                dwg.Freeze();
            }

            return dwg;
        }

        public static ImageSource SwapColors(ImageSource imageSource, ColorCallback colorCallback)
        {
            if (colorCallback == null)
                throw new ArgumentNullException("colorCallback");

            var source = imageSource;
            if (imageSource == null)
                return source;

            var image = imageSource as DrawingImage;
            if (image != null)
            {
                source = image = image.Clone();
                SwapColorsWithoutCloning(image.Drawing, colorCallback);
                source.Freeze();
                return source;
            }

            var bitmapSource = imageSource as BitmapSource;
            if (bitmapSource == null)
                throw new ArgumentException("Image source is not supported");

            return SwapColors(bitmapSource, colorCallback);
        }

        public static BitmapSource SwapColors(BitmapSource bitmapSource, ColorCallback colorCallback)
        {
            if (colorCallback == null)
                throw new ArgumentNullException("colorCallback");

            var source = bitmapSource;
            if (bitmapSource != null)
            {
                var destinationFormat = PixelFormats.Bgra32;
                BitmapPalette destinationPalette = null;
                var alphaThreshold = 0;
                var bitmap = new FormatConvertedBitmap(bitmapSource, destinationFormat, destinationPalette, alphaThreshold);
                var pixelWidth = bitmap.PixelWidth;
                var pixelHeight = bitmap.PixelHeight;
                var stride = 4 * pixelWidth;
                var pixels = new byte[stride * pixelHeight];
                bitmap.CopyPixels(pixels, stride, 0);
                
                for (var i = 0; i < pixels.Length; i += 4)
                {
                    var color = Color.FromArgb(pixels[i + 3], pixels[i + 2], pixels[i + 1], pixels[i]);
                    var color2 = colorCallback(color);
                    if (color2 == color) 
                        continue;

                    pixels[i] = color2.B;
                    pixels[i + 1] = color2.G;
                    pixels[i + 2] = color2.R;
                    pixels[i + 3] = color2.A;
                }

                source = BitmapSource.Create(pixelWidth, pixelHeight, bitmap.DpiX, bitmap.DpiY, destinationFormat, destinationPalette, pixels, stride);
                source.Freeze();
            }

            return source;
        }

        private static void SwapColorsWithoutCloning(Brush brush, ColorCallback colorCallback)
        {
            if (brush != null)
            {
                var slBrush = brush as SolidColorBrush;
                if (slBrush != null)
                {
                    slBrush.Color = colorCallback(slBrush.Color);
                }
                else
                {
                    var grBrush = brush as GradientBrush;
                    if (grBrush == null)
                    {
                        var dwgBrush = brush as DrawingBrush;
                        if (dwgBrush != null)
                        {
                            SwapColorsWithoutCloning(dwgBrush.Drawing, colorCallback);
                        }
                        else
                        {
                            var imgBrush = brush as ImageBrush;
                            if (imgBrush != null)
                            {
                                imgBrush.ImageSource = SwapColorsWithoutCloningIfPossible(imgBrush.ImageSource, colorCallback);
                            }
                            else if (!(brush is VisualBrush))
                            {
                                throw new ArgumentException("Brush Type is not supported");
                            }
                        }
                    }
                    else
                    {
                        foreach (var stop in grBrush.GradientStops)
                        {
                            stop.Color = colorCallback(stop.Color);
                        }
                    }
                }
            }
        }

        private static void SwapColorsWithoutCloning(Drawing drawing, ColorCallback colorCallback)
        {
            if (drawing != null)
            {
                var group = drawing as DrawingGroup;
                if (group != null)
                {
                    for (var i = 0; i < group.Children.Count; i++)
                    {
                        SwapColorsWithoutCloning(group.Children[i], colorCallback);
                    }
                }
                else
                {
                    var drawing2 = drawing as GeometryDrawing;
                    if (drawing2 != null)
                    {
                        SwapColorsWithoutCloning(drawing2.Brush, colorCallback);
                        if (drawing2.Pen != null)
                        {
                            SwapColorsWithoutCloning(drawing2.Pen.Brush, colorCallback);
                        }
                    }
                    else
                    {
                        var drawing3 = drawing as GlyphRunDrawing;
                        if (drawing3 != null)
                        {
                            SwapColorsWithoutCloning(drawing3.ForegroundBrush, colorCallback);
                        }
                        else
                        {
                            var drawing4 = drawing as ImageDrawing;
                            if (drawing4 != null)
                            {
                                drawing4.ImageSource = SwapColorsWithoutCloningIfPossible(drawing4.ImageSource, colorCallback);
                            }
                            else if (!(drawing is VideoDrawing))
                            {
                                throw new ArgumentException("Drawing type is not supported");
                            }
                        }
                    }
                }
            }
        }

        private static ImageSource SwapColorsWithoutCloningIfPossible(ImageSource imageSource, ColorCallback colorCallback)
        {
            var source = imageSource;
            if (imageSource == null)
            {
                return source;
            }

            var image = imageSource as DrawingImage;
            if (image != null)
            {
                SwapColorsWithoutCloning(image.Drawing, colorCallback);
                return source;
            }

            var bitmapSource = imageSource as BitmapSource;
            if (bitmapSource == null)
                throw new ArgumentException("Image source is not supported");

            return SwapColors(bitmapSource, colorCallback);
        }
    }
}
